#include <complex.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_thread.h>
#include <SDL_ttf.h>
#include <jack/jack.h>
#include <getopt.h>
#include <fftw3.h>
#include <errno.h>
#include <usb.h>

#include "main.h"
#include "cal_file.h"
#include "envelopes.h"
#include "serial.h"
#include "optimize.h"

#define VERSION "0.3"

#define PI 3.1415926535897932384626434
#define FEET_PER_USEC 983.571056
#define PIx2 (PI * 2.0)
#define PIx4 (PI * 4.0)
#define PIx6 (PI * 6.0)
#define RADIANS_PER_DEGREE (PI / 180.0)
#define DEGREES_PER_RADIAN (180.0 / PI)
#define SPACING 290.0
#define STAGGER 70.0
#define TYPE_OMNI 0
#define TYPE_CARD 1
#define TYPE_OMNI_STR "omni"
#define TYPE_CARD_STR "card"
#define TYPE_LINEAR 1
#define TYPE_GRID 2
#define TYPE_CIRCLE 3
#define TYPE_CIRCLE_CENTER 4
#define TYPE_LINEAR_STR "linear"
#define TYPE_GRID_STR "grid"
#define TYPE_CIRCLE_STR "circle"
#define TYPE_CIRCLE_CENTER_STR "circle-center"

#define BAND_MAX 6

#define CAL_ANTENNA 0
#define CAL_RECEIVER 1

//#define FFT_N 2048
//#define FFT_N 512
#define MAX_FFT 2048
unsigned int FFT_N = 512;

#define FONT_PATH "/usr/share/fonts/truetype/ttf-bitstream-vera/VeraMono.ttf"

#define SGN(x) ((x) > 0 ? 1 : ((x) == 0 ? 0 : (-1)))
#define ABS(x) ((x) > 0 ? (x) : (-x))

#define VFO_VENDOR_ID	0x16c0
#define VFO_PRODUCT_ID  0x05dc

void make_channel(jack_client_t *client, int i, char *port_name);
void cleanup(void);
int store(char *path, void *data);
int gfx_thread(void *foo);
int PrintStrings(SDL_Surface *sDest, TTF_Font *fnt, char *str, SDL_Rect *rc, SDL_Color clrFg, SDL_Color clrBg);
int ComputeIQBalance(int channel, jack_default_audio_sample_t *x, jack_default_audio_sample_t *y);
int BalanceIQ(jack_default_audio_sample_t *x, jack_default_audio_sample_t *y);
int DumpIQPoints(FILE *file, int channel);
void set_optimal_coefficients(int print_info_flag);

int band_center[BAND_MAX] = {1355000, 1455000, 1555000, 1655000, 1855000, 1855001};

int num_inputs = 0;

SDL_Thread *gt;
SDL_Surface *screen;
SDL_Surface *background_image;
SDL_Rect win, dest;
TTF_Font* font;

char client_name[256];

jack_port_t *input_ports[MAX_PORTS];
jack_port_t *output_ports[MAX_PORTS];
jack_client_t *client;

int serial_desc = 0;
struct usb_dev_handle *usb_handle = 0;

int left_shift, left_alt, left_ctrl, right_shift, right_alt, right_ctrl, shift_key, alt_key, ctrl_key;

char *cal_file_path = 0;
int center_x = 160, center_y = 160;
int band = BAND_MAX - 2;
float element_spacing = SPACING;
float element_stagger = STAGGER;
int array_type = TYPE_LINEAR;
int element_type = TYPE_CARD;
float wave_length;
int channel = 0;
int array_base_channel = 0;
float angle = 0.0;
float layout_bias = 67.0;    // physical broadside angle from north
float steering_angle = 0.0;  // steered angle away from broadside
int relay_direction = 0;     // true if steering angle > PI/2 or < -PI/2
int default_direction = 1;   // used when steering does not control relay
int uni_directional = 1;     // indicates cardioid element pattern
float bearing_angle = 0.0;   // compass heading of beam
float last_bearing;
float threshold = 0.05;      // signal level threshold for calibration tracking
int bypass = 0;
int array = 1;
int in_phase = 0;
float scale = 1.0;
int int_scale = 1;
unsigned int tuning;
int avoid_center = 0;        // flag to avoid the DC hump for calibration
int tuning_mode;
int calibration = 0;
int calibration_mode = CAL_ANTENNA;
int correction = 0;
int equalization = 1;
int rotation = 0;
int elements = 0;
int elements1 = 0;
int elements2 = 0;
int dump = 0;
int dump_count[MAX_CHANNELS];
int dump_bytes[MAX_CHANNELS];
FILE* cal_file;
FILE* dump_file[MAX_CHANNELS];
int nframes_max;
complex max_so_far = 0 + I * 0;
int max_bin;
int max_bin_valid;
complex z;
int do_jack_connect = 1;
int do_serial_control = 0;
int do_usb_control = 0;
int invert_iq = 0;
int iq_count = 0;
double gain_compensation = 0;
int debug_flag, debug_flag2, debug_flag3;
struct {double r; double t;} polar[MAX_CHANNELS];
double max_directivity;
int number_receivers = 4;
int compass_position_x;
int compass_position_y;

fftw_complex *fft_in, *fft_out, *fft_in_inv, *fft_out_inv;
fftw_plan fft_plan, fft_plan_inv;

#define MAX_SAMPLES 20

double magnitude[MAX_CHANNELS];
double mag_phase[MAX_CHANNELS];
double raw_magnitude[MAX_CHANNELS][MAX_SAMPLES];
double raw_mag_phase[MAX_CHANNELS][MAX_SAMPLES];
double avg_magnitude_sys[MAX_CHANNELS];  // product of other two
double avg_mag_phase_sys[MAX_CHANNELS];  // sum of other two
double avg_magnitude_ant[MAX_CHANNELS];  // saved across invokations
double avg_mag_phase_ant[MAX_CHANNELS];  // saved across invokations
double avg_magnitude_rec[MAX_CHANNELS];  // saved across invokations
double avg_mag_phase_rec[MAX_CHANNELS];  // saved across invokations
double ref_magnitude[MAX_SAMPLES];
double ref_mag_phase[MAX_SAMPLES];
float gain[MAX_CHANNELS][MAX_FFT];
float phase[MAX_CHANNELS][MAX_FFT];
int count[MAX_CHANNELS][MAX_FFT];
float GainX[MAX_CHANNELS];
float PhaseX[MAX_CHANNELS];
int sample_idx;
int frequency_domain = 0;
int reference_channel = 0;
int sample_offset = 0;

int snapshot_count;
complex snapshot_af[MAX_CHANNELS];
complex snapshot_iq[MAX_CHANNELS];

double cart_x[MAX_CHANNELS];
double cart_y[MAX_CHANNELS];

double window[MAX_FFT];

struct ChannelInfo {
    float array_coeff;
    float array_phase;
} multiplier[MAX_CHANNELS];

struct CoeffInfo {
    int elements;
    float array_coeff[MAX_CHANNELS];
    float array_phase[MAX_CHANNELS];
    char *name;
} coeffs[] = {
    {0, {1, 1, 1, 1}, {0}, "Automatic Optimization"},
    {0, {1, 1, 1, 1}, {0}, "Uniform"},
    {4, {1, 1.0063, 1.0063, 1}, {0}, "Optimal"},
    {4, {1, 2.0000, 2.0000, 1}, {0}, "1:2:2:1 weighting"},
    {2, {1, 1}, {0}, "Binomial"},
    {3, {1, 2, 1}, {0}, "Binomial"},
    {4, {1, 3, 3, 1}, {0}, "Binomial"},
    {5, {1, 4, 6, 4, 1}, {0}, "Binomial"},
    {6, {1, 5, 10, 10, 5, 1}, {0}, "Binomial"},
    {7, {1, 6, 15, 20, 15, 6, 1}, {0}, "Binomial"},
    {8, {1, 7, 21, 35, 35, 21, 7, 1}, {0}, "Binomial"},
    {8, {1, 1, 1, 1, 1, 1, 1, 1}, {-106,  106,  000, -106,  106, -106,  000,  106}, "5"},
    {8, {1, 1, 1, 1, 1, 1, 1, 1}, { 106, -106,  106,  000, -106,  106, -106,  000}, "6"},
    {8, {1, 1, 1, 1, 1, 1, 1, 1}, { 000,  106, -106,  106,  000, -106,  106, -106}, "7"},
    {8, {1, 1, 1, 1, 1, 1, 1, 1}, {-106,  000,  106, -106,  106,  000, -106,  106}, "8"},
    {8, {1, 1, 1, 1, 1, 1, 1, 1}, { 106, -106,  000,  106, -106,  106,  000, -106}, "1"},
    {8, {1, 1, 1, 1, 1, 1, 1, 1}, {-106,  106, -106,  000,  106, -106,  106,  000}, "2"},
    {8, {1, 1, 1, 1, 1, 1, 1, 1}, { 000, -106,  106, -106,  000,  106, -106,  106}, "3"},
    {8, {1, 1, 1, 1, 1, 1, 1, 1}, { 106,  000, -106,  106, -106,  000,  106, -106}, "4"},
};
#define NUM_COEFF_SCHEMES (sizeof(coeffs) / sizeof(coeffs[0]))
int coeff_scheme_index = 2;

inline void SumCalibration(void) {
    int i;

    for (i = 0; i < number_receivers; i++) {
        avg_magnitude_sys[i] = avg_magnitude_ant[i] * avg_magnitude_rec[i];
        avg_mag_phase_sys[i] = avg_mag_phase_ant[i] + avg_mag_phase_rec[i];
    }
}

inline void GoFrequencyDomain(jack_default_audio_sample_t *x, jack_default_audio_sample_t *y) {
    int bin;

    // move to the frequency domain if not there already
    if (!frequency_domain) {
        // populate FFT input buffer and apply window
        for (bin = 0; bin < FFT_N; bin++) {
            fft_in[bin] = (x[bin] + I * y[bin]) * window[bin];
        }
        fftw_execute(fft_plan);

        frequency_domain = 1;
    }
}

void DrawPixel(SDL_Surface *surface, unsigned int x, unsigned int y, SDL_Color color) {
    unsigned int *pixels = surface->pixels;
    if (x >= surface->w)
        return;
    if (y >= surface->h)
        return;
    int index = x + y * surface->w;
    pixels[index] = (color.r << 16) | (color.g << 8) | color.b;
}

/* Basic Bresenham line algorithm */
void DrawLine(SDL_Surface *surface, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, SDL_Color color) {
    int lg_delta, sh_delta, cycle, lg_step, sh_step;

    lg_delta = x2 - x1;
    sh_delta = y2 - y1;
    lg_step = SGN(lg_delta);
    lg_delta = ABS(lg_delta);
    sh_step = SGN(sh_delta);
    sh_delta = ABS(sh_delta);
    if (sh_delta < lg_delta) {
        cycle = lg_delta >> 1;
        while (x1 != x2) {
            DrawPixel(surface, x1, y1, color);
            cycle += sh_delta;
            if (cycle > lg_delta) {
                cycle -= lg_delta;
                y1 += sh_step;
            }
            x1 += lg_step;
        }
        DrawPixel(surface, x1, y1, color);
    }
    cycle = sh_delta >> 1;
    while (y1 != y2) {
        DrawPixel(surface, x1, y1, color);
        cycle += lg_delta;
        if (cycle > sh_delta) {
            cycle -= sh_delta;
            x1 += lg_step;
        }
        y1 += sh_step;
    }
    DrawPixel(surface, x1, y1, color);
}

// Since the following functions iterate a circle, the following trig identities allows us to
// avoid computing the value of sines and cosines functions inside the loop:
//
// sin(a + na) = sin(a(1+n)) = sin(a)*cos(na)+cos(a)*sin(na)
// cos(a + na) = cos(a(1+n)) = cos(a)*cos(na)-sin(a)*sin(na)
//
// We choose a value of a as the loop iteration angle, and precompute the sine and cosine of
// that value.  We also save the value of the sine and cosine of the current iteration to use in
// the next iteration.

void DrawPattern(SDL_Surface *dest, SDL_Rect *rc) {
    SDL_Color pattern_color = {0, 255, 0, 0};
    SDL_Color element_color = {255, 255, 0, 0};
    double radius = 120.0;
    double element_radius = 90.0;
    int n, x, y;
    double bias, theta, f;
    double sin_theta = 0.0, cos_theta = 1.0, sin_last_theta = 0.0, cos_last_theta = 1.0;
    double k = 2 * PI / wave_length;
    double maximum;
    double sin_increment, cos_increment;

    // get the layout bias in radians
    if (array_type == TYPE_LINEAR) {
        bias = (90.0 - layout_bias) * RADIANS_PER_DEGREE;
    } else {
        bias = 0;
    }

    // draw cross marks (plus signs) for each array element
    maximum = 0;
    for (n = 0; n < elements; n++) {
        if (polar[n].r > maximum)
            maximum = polar[n].r;
    }
    for (n = 0; n < elements; n++) {
        x = polar[n].r / maximum * element_radius * cos(polar[n].t - bias) + 0.5;
        y = polar[n].r / maximum * element_radius * sin(polar[n].t - bias) + 0.5;
        // draw a plus
        DrawLine(dest, center_x + x - 5, center_y + y, center_x + x + 5, center_y + y, element_color);
        DrawLine(dest, center_x + x, center_y + y - 5, center_x + x, center_y + y + 5, element_color);
    }
    maximum = 0;

    // precompute sine and cosine of the increment
    sin_increment = sin(0.5);
    cos_increment = cos(0.5);
    // iterate a circle to determine maximum magnitude
    for (theta = 0.0; theta < 2.0 * PI; theta += 0.01) {
        // compute sin(theta) and cos(theta)
        if (theta == 0.0) {
            sin_last_theta = 0.0;
            cos_last_theta = 1.0;
            sin_theta = 0.0;
            cos_theta = 1.0;
        } else {
            sin_last_theta = sin_theta;
            cos_last_theta = cos_theta;
            sin_theta = sin_increment * cos_last_theta + cos_increment * sin_last_theta;
            cos_theta = cos_increment * cos_last_theta - sin_increment * sin_last_theta;
        }
        // iterate the array elements
        complex E = 0;
        for (n = 0; n < elements; n++) {
            double C = multiplier[n].array_coeff;
            double A = multiplier[n].array_phase * RADIANS_PER_DEGREE;
            if (coeff_scheme_index == 0) {
                double cos_diff = cos_theta * cos(polar[n].t) + sin_theta * sin(polar[n].t);
                E += C * cexp(I * (-k * polar[n].r * cos_diff + A));
            } else {
                E += C * cexp(I * (k * element_spacing * n * sin_theta + A));
            }
        }
        f = cabs(E);
        if (uni_directional) {
            // multiply by the element pattern (cardiod)
            if (relay_direction) {
                f *= 1.0 - cos_theta;
            } else {
                f *= 1.0 + cos_theta;
            }
        }
        if (f > maximum) {
            maximum = f;
        }
    }

    // precompute sine and cosine of the increment
    sin_increment = sin(0.01);
    cos_increment = cos(0.01);
    // iterate a circle to plot
    for (theta = 0.0; theta < 2.0 * PI; theta += 0.01) {
        // compute sin(theta) and cos(theta)
        if (theta == 0.0) {
            sin_last_theta = 0.0;
            cos_last_theta = 1.0;
            sin_theta = 0.0;
            cos_theta = 1.0;
        } else {
            sin_last_theta = sin_theta;
            cos_last_theta = cos_theta;
            sin_theta = sin_increment * cos_last_theta + cos_increment * sin_last_theta;
            cos_theta = cos_increment * cos_last_theta - sin_increment * sin_last_theta;
        }
        // iterate the array elements
        complex E = 0;
        for (n = 0; n < elements; n++) {
            double C = multiplier[n].array_coeff;
            double A = multiplier[n].array_phase * RADIANS_PER_DEGREE;
            // optimized element excitations has phase progression built-in whereas the fixed
            // excitations don't
            if (coeff_scheme_index == 0) {
                double cos_diff = cos_theta * cos(polar[n].t) + sin_theta * sin(polar[n].t);
                E += C * cexp(I * (-k * polar[n].r * cos_diff + A));
            } else {
                E += C * cexp(I * (k * element_spacing * n * sin_theta + A));
            }
        }
        f = cabs(E);
        if (uni_directional) {
            // multiply by the element pattern (cardiod)
            if (relay_direction) {
                f *= 1.0 - cos_theta;
            } else {
                f *= 1.0 + cos_theta;
            }
        }
        // eliminate details more than 20 db down, scale by max array directivity, and derive
        // cartesian coordinates from polar coordinates
        if (f / maximum >= 0.01) {
            x = radius * log10(100.0 * f / maximum) / log10(100.0) * cos(theta - bias) + 0.5;
            y = radius * log10(100.0 * f / maximum) / log10(100.0) * sin(theta - bias) + 0.5;
        } else {
            x = y = 0;
        }
        // draw a dot
        DrawPixel(dest, center_x + x, center_y + y, pattern_color);
    }
}

void DrawCompass(SDL_Surface *dest, SDL_Rect *rc) {
    double theta;
    double radius = 120.0;
    SDL_Surface *surface;
    int width = 320;
    int height = 320;
    SDL_Color foreground = {255, 255, 255, 0};
    SDL_Color background = {0, 0, 0, 0};
    SDL_Color pointer = {255, 0, 0, 0};
    int bits_per_pixel, bytes_per_pixel;
    int x_pos, x1_pos, y_pos, y1_pos;
    SDL_Rect buf_rect;
    double tick_end = 8.0;
    double sin_theta = 0.0, cos_theta = 1.0, sin_last_theta = 0.0, cos_last_theta = 1.0;

    // Make the surface to which to blit the text:
    surface = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 
                                 (bits_per_pixel = dest->format->BitsPerPixel), 
                                 dest->format->Rmask,
                                 dest->format->Gmask,
                                 dest->format->Bmask,
                                 dest->format->Amask);
    bytes_per_pixel = bits_per_pixel / 8;
    // precompute sine and cosine of the increment
    double sin_increment = sin(0.1 * RADIANS_PER_DEGREE);
    double cos_increment = cos(0.1 * RADIANS_PER_DEGREE);
    // Draw labels and circle with tick marks and cross hair
    DrawLine(surface, center_x - 10, center_y, center_x + 10, center_y, foreground);
    DrawLine(surface, center_x, center_y - 10, center_x, center_y + 10, foreground);
    for (theta = 0; theta < 360.0; theta += 0.1) {
        // compute sin(theta)
        if (theta == 0.0) {
            sin_last_theta = 0.0;
            cos_last_theta = 1.0;
            sin_theta = 0.0;
            cos_theta = 1.0;
        } else {
            sin_last_theta = sin_theta;
            cos_last_theta = cos_theta;
            sin_theta = sin_increment * cos_last_theta + cos_increment * sin_last_theta;
            cos_theta = cos_increment * cos_last_theta - sin_increment * sin_last_theta;
        }
        int x_pos = (int)(radius * cos_theta + 0.5) + center_x;
        int y_pos = (int)(radius * sin_theta + 0.5) + center_y;
        DrawPixel(surface, x_pos, y_pos, foreground);
        // draw a tick mark every 30 degrees
        if (ABS(fmod(theta, 30.0)) < 0.1) {
            int x1 = (int)((radius + tick_end) * cos_theta + 0.5) + center_x;
            int y1 = (int)((radius + tick_end) * sin_theta + 0.5) + center_y;
            int x2 = (int)((radius - tick_end) * cos_theta + 0.5) + center_x;
            int y2 = (int)((radius - tick_end) * sin_theta + 0.5) + center_y;
            DrawLine(surface, x1, y1, x2, y2, foreground);
        }
    }
    // Draw pointer for current bearing
    double tweak = -1.0;
    x_pos = (int)((radius + tweak) * cos((bearing_angle - 90.0) * RADIANS_PER_DEGREE) + 0.5) + center_x;
    y_pos = (int)((radius + tweak) * sin((bearing_angle - 90.0) * RADIANS_PER_DEGREE) + 0.5) + center_y;
    DrawLine(surface, center_x, center_y, x_pos, y_pos, pointer);
    // Draw pointer header
    tweak = 0.1;
    x1_pos = x_pos + (int)((radius * tweak) * cos((bearing_angle - 90.0 + 180.0 + 30.0) * RADIANS_PER_DEGREE) + 0.5);
    y1_pos = y_pos + (int)((radius * tweak) * sin((bearing_angle - 90.0 + 180.0 + 30.0) * RADIANS_PER_DEGREE) + 0.5);
    DrawLine(surface, x_pos, y_pos, x1_pos, y1_pos, pointer);
    x1_pos = x_pos + (int)((radius * tweak) * cos((bearing_angle - 90.0 + 180.0 - 30.0) * RADIANS_PER_DEGREE) + 0.5);
    y1_pos = y_pos + (int)((radius * tweak) * sin((bearing_angle - 90.0 + 180.0 - 30.0) * RADIANS_PER_DEGREE) + 0.5);
    DrawLine(surface, x_pos, y_pos, x1_pos, y1_pos, pointer);
    // precompute sine and cosine of the increment
    sin_increment = sin(30.0 * RADIANS_PER_DEGREE);
    cos_increment = cos(30.0 * RADIANS_PER_DEGREE);
    for (theta = 0.0; theta < 360.0; theta += 30.0) {
        // compute sin(theta)
        if (theta == 0.0) {
            sin_last_theta = 0.0;
            cos_last_theta = 1.0;
            sin_theta = 0.0;
            cos_theta = 1.0;
        } else {
            sin_last_theta = sin_theta;
            cos_last_theta = cos_theta;
            sin_theta = sin_increment * cos_last_theta + cos_increment * sin_last_theta;
            cos_theta = cos_increment * cos_last_theta - sin_increment * sin_last_theta;
        }
        int width, height;
        char label[10];
        snprintf(label, sizeof(label), "%d", (int)(theta < 270.0 ? theta + 90.0 : theta - 270.0));
        TTF_SizeText (font, label, &width, &height);
        // position the center of the text beyond the tick mark.  calculate the horizontal and
        // vertical offset according to the bearing
        x_pos = (int)((radius + tick_end) * cos_theta + 0.5) + center_x;
        y_pos = (int)((radius + tick_end) * sin_theta + 0.5) + center_y;
        x_pos += (int)(width / 2 * cos_theta + 0.5) - width / 2;
        y_pos += (int)(height / 2 * sin_theta + 0.5) - height / 2;
        buf_rect.x = x_pos;
        buf_rect.y = y_pos;
        buf_rect.w = width;
        buf_rect.h = height;
        PrintStrings(surface, font, label, &buf_rect, foreground, background);
    }
    // Draw the array pattern
    DrawPattern(surface, rc);
    // Draw the compass on the destination:
    SDL_BlitSurface(surface, NULL, dest, rc);
    SDL_FreeSurface(surface);
}

void AdjustAngles(void) {
    // normalize compass heading
    if (bearing_angle >= 360.0) {
        bearing_angle -= 360.0;
    } else if (bearing_angle < 0.0) {
        bearing_angle += 360.0;
    }
    // remove array orientation bias and convert to mathematical angle
    if (array_type == TYPE_LINEAR) {
        steering_angle = (layout_bias - bearing_angle) * RADIANS_PER_DEGREE;
    } else {
        //steering_angle = (0.0 - (bearing_angle - layout_bias)) * RADIANS_PER_DEGREE;
        steering_angle = (bearing_angle - layout_bias - 180.0) * RADIANS_PER_DEGREE;
    }
    // normalize math angle to -PI to PI
    if (steering_angle > PI) {
        steering_angle -= 2.0 * PI;
    } else if (steering_angle < -PI) {
        steering_angle += 2.0 * PI;
    }
    // if using unidirectional elements ...
    if (uni_directional) {
        // split math angle into positive and negative half spaces and mirror the angle
        // for the negative half space
        if ((steering_angle < -PI / 2.0) || (steering_angle > PI / 2.0)) {
            relay_direction = 1;
            steering_angle = PI - steering_angle;
            if (steering_angle > PI) {
                steering_angle -= 2.0 * PI;
            }
        } else {
            relay_direction = 0;
        }
    }
    // if in "optimized" mode, update the array factors
    //if (array_type != TYPE_CIRCLE)
        if (coeff_scheme_index == 0) {
            set_optimal_coefficients(1);
        }
}

void usb_error(int code, char *name) {
    if (code < 0)
        printf("%s returned %d: %s\n", name, code, strerror(code));
    else
        printf("%s returned %d bytes\n", name, code);
}

#define F_LO 4850
#define F_HI 5670
double crystal_freq = 114.285;

// This hasn't been tested yet.  I don't recall whether or not 0x50 is the correct code.
int UsbSetRelay(struct usb_dev_handle *handle, int state) {
    unsigned char buffer[6];
    buffer[0] = 0;
    buffer[1] = 0;
    buffer[2] = 0;
    buffer[3] = 0;
    buffer[4] = 0;
    buffer[5] = 0;
    state &= 1;  // low order bit only
    int result = usb_control_msg(usb_handle,
                                 USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, 
                                 0x50, state, 0, (char *)buffer, sizeof(buffer), 5000);
    usb_error(result, "usb_control_msg()");
    return 0;
}

int UsbSetFrequency(struct usb_dev_handle *handle, double frequency) {
    char HS_divisor[] = {4,5,6,7,-1,9,-1,11};
    int i;
    double y;
    struct solution {int HS_DIV_index; int N1; double f0;};
    struct solution solutions[sizeof(HS_divisor)];

    printf("frequency = %.13f\n", frequency);

    // Iterate the high-speed divisors and calculate the other divisor and DCO frequency.
    for (i = sizeof(HS_divisor) - 1; i >= 0; i--) {
        // preset solution DCO frequency
        solutions[i].f0 = F_HI + 1;
        if (HS_divisor[i] < 0)
            continue;
        solutions[i].HS_DIV_index = i;
        // calculate total divider ratio
        y = (F_HI + F_LO) / 2 / frequency;
        // calculate N1 and adjust to spec
        y /= HS_divisor[i];
        if (y < 1.5) {
            solutions[i].N1 = 0;
        } else {
            solutions[i].N1 = 2 * ((int)(y) / 2) - 1;
        }
        if (solutions[i].N1 > 127)
            solutions[i].N1 = 127;
        // calculate DCO frequency
        solutions[i].f0 = frequency * (solutions[i].N1 + 1) * HS_divisor[i];
        printf("f0 = %f, N1 = %d, HS_DIV_index = %d\n", solutions[i].f0, solutions[i].N1, solutions[i].HS_DIV_index);
    }

    // Iterate the solutions and choose the solution with the lowest VCO frequency.
    int N1_min = 128;
    int index_of_min = -1;
    for (i = 0; i < sizeof(solutions); i++) {
        if (solutions[i].f0 < F_LO || solutions[i].f0 > F_HI)
            continue;
        if (solutions[i].N1 < N1_min) {
            N1_min = solutions[i].N1;
            index_of_min = i;
        }
    }

    // indicate no valid solution
    if (index_of_min < 0) {
        return index_of_min;
    }
    
    printf("f0 = %f, N1 = %d, HS_DIV_index = %d\n", solutions[index_of_min].f0, solutions[index_of_min].N1, solutions[index_of_min].HS_DIV_index);
    
    // program results
    unsigned char buffer[6];
    int HS_DIV_index = solutions[index_of_min].HS_DIV_index;
    int N1 = solutions[index_of_min].N1;
    double RFREQ = solutions[index_of_min].f0 / crystal_freq;
    int RFREQ_whole = (int)RFREQ;
    int RFREQ_fract = (int)((RFREQ - RFREQ_whole) * (1 << 28));
    buffer[0] = HS_DIV_index << 5 | N1 >> 2;
    buffer[1] = (N1 & 0x03) << 6 | RFREQ_whole >> 4;
    buffer[2] = (RFREQ_whole & 0x0f) << 4 | RFREQ_fract >> 24;
    buffer[3] = RFREQ_fract >> 16;
    buffer[4] = RFREQ_fract >> 8;
    buffer[5] = RFREQ_fract;
    for (i = 0; i < sizeof(buffer); i++) {
        printf("Register %d = %02x Hex\n", i + 7, buffer[i]);
    }
    int result = usb_control_msg(usb_handle,
                                 USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, 
                                 0x30, 0x755, 0, (char *)buffer, sizeof(buffer), 5000);
    usb_error(result, "usb_control_msg()");
    return 0;
}

int SetFrequency(double frequency) {
    if (do_serial_control) {
        return SerialSetFrequency(serial_desc, frequency / 1000000.0L);
    } else if (do_usb_control) {
        return UsbSetFrequency(usb_handle, frequency / 1000000.0L);
    }
    return 0;
}

int set_elements(void) {
    if (array_type == TYPE_LINEAR) {
        return elements1;
    } else if (array_type == TYPE_GRID) {
        return elements1 * elements2;
    } else if (array_type == TYPE_CIRCLE) {
        return elements1 + elements2;
    } else if (array_type == TYPE_CIRCLE_CENTER) {
        return elements1 + elements2 + 1;
    }
    return 0;
}

void get_array_coordinates() {
    int index, local_elements = 0, local_array_type = ARRAY_TYPE_LINEAR;
    int x_avg = 0, y_avg = 0;

    if (array_type == TYPE_LINEAR) {
        local_array_type = ARRAY_TYPE_LINEAR;
        angle += 0.0;
        local_elements = elements1;
    } else if (array_type == TYPE_GRID) {
        local_array_type = ARRAY_TYPE_GRID;
        angle += 90.0;
        local_elements = elements1 * elements2;
    } else if (array_type == TYPE_CIRCLE) {
        local_array_type = ARRAY_TYPE_CIRCLE;
        angle += 90.0;
        local_elements = elements1 + elements2;
    } else if (array_type == TYPE_CIRCLE_CENTER) {
        local_array_type = ARRAY_TYPE_CIRCLE_CENTER;
        angle += 90.0;
        local_elements = elements1 + elements2 + 1;
    }

    CreateArray(elements1, elements2, local_array_type, element_spacing, element_stagger, cart_x, cart_y);
    // convert the rectangular coordinates to polar coordinates and center the array
    for (index = 0; index < local_elements; index++) {
        x_avg += cart_x[index];
        y_avg += cart_y[index];
    }
    x_avg /= local_elements;
    y_avg /= local_elements;
    for (index = 0; index < local_elements; index++) {
        polar[index].r = hypot(cart_x[index] - x_avg, cart_y[index] - y_avg);
        polar[index].t = atan2(cart_y[index] - y_avg, cart_x[index] - x_avg);
    }
}

void set_optimal_coefficients(int print_info_flag) {
    complex result[elements], result1[elements], result2[elements];
    int index;
    double directivity, directivity1, directivity2;
    double angle = steering_angle * DEGREES_PER_RADIAN;
    int local_elements = 0, local_array_type = ARRAY_TYPE_LINEAR;
    double local_gain_comp;
    char *type_str[] = {"linear", "grid", "circle", "circle & center"};

    if (array_type == TYPE_LINEAR) {
        local_array_type = ARRAY_TYPE_LINEAR;
        angle += 0.0;
        local_elements = elements1;
    } else if (array_type == TYPE_GRID) {
        local_array_type = ARRAY_TYPE_GRID;
        angle += 90.0;
        local_elements = elements1 * elements2;
    } else if (array_type == TYPE_CIRCLE) {
        local_array_type = ARRAY_TYPE_CIRCLE;
        angle += 90.0;
        local_elements = elements1 + elements2;
    } else if (array_type == TYPE_CIRCLE_CENTER) {
        local_array_type = ARRAY_TYPE_CIRCLE_CENTER;
        angle += 90.0;
        local_elements = elements1 + elements2 + 1;
    }

    // attempt to determine maximum directivity
    if (max_directivity == 0) {
        OptimizeArray(local_elements, cart_x, cart_y, wave_length, 0.0, result1, &directivity1);
        OptimizeArray(local_elements, cart_x, cart_y, wave_length, 90.0, result2, &directivity2);
        if (directivity1 > directivity2) {
            max_directivity = directivity1;
        } else {
            max_directivity = directivity2;
        }
    }
    memset(result, 0, sizeof(result));
    if (print_info_flag)
        printf("Optimize %d el %s, lambda %8.3f, dim 1 %8.3f, dim 2 %8.3f, angle %8.3f\n", 
               local_elements, type_str[local_array_type - 1], wave_length, element_spacing, element_stagger, angle);
    // determine high directivity direction - usually at 0 or 90 degrees
    OptimizeArray(local_elements, cart_x, cart_y, wave_length, angle, result, &directivity);
    // compute sum of element magnitude coefficients
    local_gain_comp = 0;
    for (index = 0; index < local_elements; index++) {
        multiplier[index].array_coeff = cabs(result[index]) / cabs(result[0]);
        multiplier[index].array_phase = carg(result[index]) * DEGREES_PER_RADIAN;
        local_gain_comp += cabs(result[index]) / cabs(result[0]);
    }
    // print the results
    if (print_info_flag) {
        printf("Magnitude    Phase     Step\n");
        int x;
        for (x = 0; x < local_elements; x++) {
            double step = 0;
            // calculate phase difference between successive elements
            if (x > 0) {
                step = (carg(result[x]) - carg(result[x-1])) * DEGREES_PER_RADIAN;
                if (step < -180.0) {
                    step += 360.0;
                } else if (step > 180.0) {
                    step -= 360.0;
                }
            }
            if (local_array_type == ARRAY_TYPE_LINEAR)
                printf("%9.3f %8.3f %8.3f\n", cabs(result[x]) / cabs(result[0]),
                       (carg(result[x]) - carg(result[0])) * DEGREES_PER_RADIAN, step);
            else 
                printf("%9.3f %8.3f %8.3f\n", cabs(result[x]) / cabs(result[0]),
                       carg(result[x]) * DEGREES_PER_RADIAN, step);

        }
        printf("%9.3f\n", local_gain_comp);
        printf("max directivity = %0.2f, directivity = %0.2f\n", max_directivity, directivity);
    }
    //gain_compensation = local_gain_comp;
    gain_compensation = 4;
    printf("gain compensation = %0.2f\n", gain_compensation);
}

void set_coefficients() {
    int index;
    double local_gain_comp;

    local_gain_comp = 0;
    //set_optimal_coefficients(1);
    if (/* array_type != TYPE_CIRCLE && */ coeff_scheme_index == 0) {
        max_directivity = 0;
        set_optimal_coefficients(1);
    } else {
        for (index = 0; index < MAX_CHANNELS; index++) {
            multiplier[index].array_coeff = coeffs[coeff_scheme_index].array_coeff[index];
            multiplier[index].array_phase = coeffs[coeff_scheme_index].array_phase[index];
            local_gain_comp += coeffs[coeff_scheme_index].array_coeff[index];
        }
        //gain_compensation = local_gain_comp;
        gain_compensation = 4;
        printf("gain compensation = %0.2f\n", gain_compensation);
    }
    last_bearing = -1;  // force pattern redraw
}

void SignalActioner(int signal, siginfo_t *info, void *param) {
    printf("trapped %d @ %x\n", signal, (unsigned int)info->si_addr);
    exit(1);
}

char *help_text = {
    "usage: phasor [options] ports\n"
    "  -1 number  element spacing or radius in feet (float) of 1st row, circle, etc.\n"
    "  -2 number  element spacing or radius in feet (float) of 2nd row, circle, etc.\n"
    "  -a string  array type, one of 'linear', 'grid', 'circle', or 'circle-center'\n"
    "  -b number  array layout bias (angle) degrees east of north (float)\n"
    "  -d string  control via given serial port\n"
    "  -e number  number of array elements (integer)\n"
    "  -h         display help and terminate\n"
    "  -i         invert I and Q channels\n"
    "  -j string  client name for jack\n"
    "  -n number  number of array elements (integer) in 1st row, circle, etc.\n"
    "  -m number  number of array elements (integer) in 2nd row, circle, etc.\n"
    "  -s number  element spacing in feet (float)\n"
    "  -t string  element type, either 'omni' or 'card'\n"
    "  -u         control via USB\n"
    "  -v number  frame size in bytes (integer)\n"
    "  -x         disable jack connections (connected  by qjackctl instead)\n"
};

int main(int argc, char *argv[])
{
    SDL_Event event;
    unsigned int i, j;
    int opt;
    int port_argv_base = 1;
    char window_name[256];
    char *us_client_name = NULL;
    char *key;
    char *serial_port = 0;
    struct usb_bus *usb_bus;
    struct usb_device *usb_dev = 0;
    struct sigaction action;

    printf("Phasor " VERSION ", Max Channels: %d\n", MAX_CHANNELS);

    action.sa_sigaction = SignalActioner;
    action.sa_flags = SA_SIGINFO;
    sigaction(SIGSEGV, &action, 0);

    num_inputs = argc;
    while ((opt = getopt(argc, argv, "a:b:c:d:s:1:2:n:m:j:v:e:t:uxih")) != -1) {
        switch (opt) {
        case 'a':
            if (strcmp(optarg, TYPE_LINEAR_STR) == 0) {
                array_type = TYPE_LINEAR;
            } else if (strcmp(optarg, TYPE_GRID_STR) == 0) {
                array_type = TYPE_GRID;
            } else if (strcmp(optarg, TYPE_CIRCLE_STR) == 0) {
                array_type = TYPE_CIRCLE;
            } else if (strcmp(optarg, TYPE_CIRCLE_CENTER_STR) == 0) {
                array_type = TYPE_CIRCLE_CENTER;
            } else {
                fprintf(stderr, "Invalid element type: \"%s\"\n", optarg);
                num_inputs = 0;
            }
            port_argv_base += 2;
            break;
        case 'b':
            layout_bias = atof(optarg);
            port_argv_base += 2;
            break;
        case 'c':
            cal_file_path = strdup(optarg);
            port_argv_base += 2;
            break;
        case 'd':
            do_serial_control = 1;
            serial_port = strdup(optarg);
            port_argv_base += 2;
            break;
        case 's':
            element_spacing = atof(optarg);
            port_argv_base += 2;
            break;
        case '1':
            element_spacing = atof(optarg);
            port_argv_base += 2;
            break;
        case '2':
            element_stagger = atof(optarg);
            port_argv_base += 2;
            break;
        case 'j':
            us_client_name = strdup(optarg);
            port_argv_base += 2;
            break;
        case 'h':
            /* Force help to be shown */
            num_inputs = 0;
            break;
        case 'v':
            FFT_N = atoi(optarg);
            port_argv_base += 2;
            break;
        case 'e':
            elements = atoi(optarg);
            port_argv_base += 2;
            break;
        case 'n':
            elements1 = atoi(optarg);
            port_argv_base += 2;
            break;
        case 'm':
            elements2 = atoi(optarg);
            port_argv_base += 2;
            break;
        case 't':
            if (strcmp(optarg, TYPE_OMNI_STR) == 0) {
                element_type = TYPE_OMNI;
                uni_directional = 0;
            } else if (strcmp(optarg, TYPE_CARD_STR) == 0) {
                element_type = TYPE_CARD;
                uni_directional = 1;
            } else {
                fprintf(stderr, "Invalid element type: \"%s\"\n", optarg);
                num_inputs = 0;
            }
            port_argv_base += 2;
            break;
        case 'u':
            do_usb_control = 1;
            port_argv_base += 1;
            break;
        case 'x':
            do_jack_connect = 0;
            port_argv_base += 1;
            break;
        case 'i':
            invert_iq = 1;
            port_argv_base += 1;
            break;
        case '?':
            fprintf(stderr, "what does this mean?\n");
            num_inputs = 0;
            break;
        default:
            fprintf(stderr, "missing argument for %c\n", opt);
            num_inputs = 0;
            break;
        }
    }
    num_inputs -= port_argv_base;

    if (elements == 0 && elements1 == 0 && elements2 == 0) {
        // nothing specified
        fprintf(stderr, "Must specify -e or -n and -m\n");
        exit(1);
    } else if (elements == 0 && elements1 == 0 && elements2 != 0) {
        // weird case
        elements = elements2;
    } else if (elements == 0 && elements1 != 0 && elements2 == 0) {
        // linear or simple circle case
        elements = elements1;
    } else if (elements == 0 && elements1 != 0 && elements2 != 0) {
        // complicated case, depends upon array type
        elements = set_elements();
    } else if (elements != 0 && elements1 == 0 && elements2 == 0) {
        // traditional case, assume linear
        elements1 = elements;
    } else if (elements != 0 && elements1 == 0 && elements2 != 0) {
        // weird case, better agree
        if (elements != elements2) {
            fprintf(stderr, "-e = %d and -m = %d, please correct\n", elements, elements2);
        }
    } else if (elements != 0 && elements1 != 0 && elements2 == 0) {
        // linear case, better agree
        if (elements != elements1) {
            fprintf(stderr, "-e = %d and -n = %d, please correct\n", elements, elements1);
        }
    } else if (elements != 0 && elements1 != 0 && elements2 != 0) {
        // complicated, better agree
        if (elements != set_elements()) {
            fprintf(stderr, "-e = %d -n = %d, and -m = %d, please correct\n", elements, elements1, elements2);
        }
    } else {
        fprintf(stderr, "logic error!\n");
        exit(1);
    }

    if (num_inputs > MAX_PORTS) {
        fprintf(stderr, "Ignoring %d ports beyond %d max\n", num_inputs - MAX_PORTS, MAX_PORTS);
        num_inputs = MAX_PORTS;
    } else {
        printf("Processing %d inputs\n", num_inputs);
    }

    if (num_inputs < 1) {
        fputs(help_text, stderr);
        exit(1);
    }

    number_receivers = num_inputs / 2;
    if (number_receivers > 4) {
        compass_position_x = 650;
        compass_position_y = 0;
    } else {
        compass_position_x = 330;
        compass_position_y = 0;
    }

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
        exit(1);
    }

    atexit(SDL_Quit);

    if (TTF_Init() == -1) {
        fprintf(stderr, "TTF_Init: %s\n", TTF_GetError());
        exit(2);
    }

    if(!(font = TTF_OpenFont(FONT_PATH, 14))) {
		printf("Error loading font: %s", TTF_GetError());
		return 1;
	}
	
    fft_in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * FFT_N);
    fft_out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * FFT_N);
    fft_in_inv = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * FFT_N);
    fft_out_inv = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * FFT_N);
    if (fft_in == 0 || fft_out == 0 || fft_in_inv == 0 || fft_out_inv == 0) {
        fprintf(stderr, "FFT initialization failed\n");
        return 1;
    }    
    fft_plan = fftw_plan_dft_1d(FFT_N, fft_in, fft_out, FFTW_FORWARD, FFTW_MEASURE);
    fft_plan_inv = fftw_plan_dft_1d(FFT_N, fft_in_inv, fft_out_inv, FFTW_BACKWARD, FFTW_MEASURE);
    if (fft_plan == 0 || fft_plan_inv == 0) {
        fprintf(stderr, "FFT initialization failed\n");
        return 1;
    }    

    /* Calculate window size */
    win.x = 0;
    win.y = 0;
    if (number_receivers > 4) {
        win.w = 960;
        win.h = 320;
    } else {
        win.w = 640;
        win.h = 320;
    }
    
    screen = SDL_SetVideoMode(win.w, win.h, 32, SDL_SWSURFACE);
    if (screen == NULL) {
        fprintf(stderr, "Unable to set %dx%d video: %s\n", win.w, win.h, SDL_GetError());
        exit(1);
    }
    
    if (number_receivers > 4) {
        dest.w = 640;
        dest.h = 320;
    } else {
        dest.w = 640;
        dest.h = 320;
    }
    dest.x = compass_position_x;
    dest.y = compass_position_y;
    DrawCompass(screen, &dest);
    last_bearing = bearing_angle;
    
    SDL_UpdateRects(screen, 1, &win);

    // Turn on key repeating with default values
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

    /* Register with jack */
    if (us_client_name) {
        strncpy(client_name, us_client_name, 31);
    } else {
        //snprintf(client_name, 255, "phasor-%d", getpid());
        snprintf(client_name, 255, "phasor");
    }
    if ((client = jack_client_new(client_name)) == 0) {
        fprintf(stderr, "jack server not running?\n");
        exit(1);
    }
    printf("Registering as \"%s\"\n", client_name);
    snprintf(window_name, 255, "Phasor %s", client_name);

    // Verify that our buffer size matches the prevailing size.  Eventually, we'll just set our
    // frame buffer size to this number.
    if (FFT_N != (nframes_max = jack_get_buffer_size (client))) {
        fprintf(stderr, "Jack buffer size %d does not match Phasor buffer size %d\n", nframes_max, FFT_N);
        exit(1);
    }

    // start graphics thread
    //    gt = SDL_CreateThread(gfx_thread, NULL);

    /* Set the jack process method */
    jack_set_process_callback(client, process_ring, 0);
    // This is used to maually save the session, but the auto stuff
    // does a better job anyway. Just here for testing
    //jack_set_store_callback(client, store, NULL);

    if (jack_activate(client)) {
        fprintf (stderr, "cannot activate client");
        exit(1);
    }

    // Compute Blackman-Harris window
    double a0 = 0.35875;
    double a1 = 0.48829;
    double a2 = 0.14128;
    double a3 = 0.01168;
    for (i = 0; i < FFT_N; i++) {
        window[i] = a0 - a1 * cos(PIx2 * i / (FFT_N - 1)) + a2 * cos(PIx4 * i / (FFT_N - 1)) - a3 * cos(PIx6 * i / (FFT_N - 1));
    }

    /* Create and connect the jack ports */
    for (i = 0; i < num_inputs; i++) {
        make_channel(client, i, argv[port_argv_base+i]);
    }

    // Get serial port
    if (do_serial_control) {
        serial_desc = AcquireSerialPort(serial_port);
        if (serial_desc == 1) {
            printf("%s open() failed: %s\n", serial_port, strerror(errno));
            exit(1);
        }
    } else if (do_usb_control) {
        usb_init();
        usb_find_busses();
        usb_find_devices();
        for (usb_bus = usb_get_busses(); usb_bus; usb_bus = usb_bus->next) {
            // printf("scanning USB bus %s\n", usb_bus->dirname);
            for (usb_dev = usb_bus->devices; usb_dev; usb_dev = usb_dev->next) {
                // printf("scanning USB device %s\n", usb_dev->filename);
                // printf("descriptor.idVendor %x\n", usb_dev->descriptor.idVendor);
                // printf("descriptor.idProduct %x\n", usb_dev->descriptor.idProduct);
                if ((usb_dev->descriptor.idVendor == VFO_VENDOR_ID) &&
                    (usb_dev->descriptor.idProduct == VFO_PRODUCT_ID)) {
                    printf("Found USB device %04x:%04x\n", usb_dev->descriptor.idVendor, usb_dev->descriptor.idProduct);
                    goto dev_found;
                }
            }
        }
dev_found:
        if (usb_dev == NULL) {
            printf("USB device %04x:%04x not found\n", VFO_VENDOR_ID, VFO_PRODUCT_ID);
            exit(1);
        }
        usb_handle = usb_open(usb_dev);
        if (usb_handle == NULL) {
            printf("Not able to open the USB device\n");
            exit(1);
        }
        int result;
        result = usb_claim_interface(usb_handle, 0);
        usb_error(result, "usb_claim_interface()");
        
#if 0
        char info[120];
        usb_get_string_simple(usb_handle, usb_dev->descriptor.iManufacturer, info, strlen(info));
        printf("Found %s\n", info);
        usb_get_string_simple(usb_handle, usb_dev->descriptor.iProduct, info, strlen(info));
        printf("Found %s\n", info);
        usb_get_string_simple(usb_handle, usb_dev->descriptor.iSerialNumber, info, strlen(info));
        printf("Found %s\n", info);
#endif
        int index = -1;
        unsigned char buffer[6];
        memset(buffer, 0, 6);

        // For the following USB transaction to work, we must run either with root permissions,
        // or change the /dev/bus/usb/bus-num/dev-num file permission to allow world write.  To
        // cause the latter effect, add a udev rule (in /etc/udev/rules.d/10-local.rules) as
        // follows:
        // 
        // SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", MODE:="0666"
        //
        // Note the colon-equals assignment which causes this rule to override the general rule
        // in 40-basic-permissions.rules.

        result = usb_control_msg(usb_handle,
                        USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, 
                                 0x3f, 0x55, index, (char *)buffer, sizeof(buffer), 5000);
        usb_error(result, "usb_control_msg()");
        for (i = 0; i < 6; i++) {
            printf("Register %d = %02x Hex\n", i + 7, buffer[i]);
        }
        // Borrowed this code from file hostware/Unit1.pas by Tom, DG8SAQ, and available from
        // http://www.mydarc.de/dg8saq/SI570/index.shtml
        long freq_fraction = (buffer[2] & 0x0f) << 24 | buffer[3] << 16 | buffer[4] << 8 | buffer[5];
        long freq_whole = (buffer[1] & 0x3f) << 4 | buffer[2] >> 4;
        double frequency = (double)freq_whole + (double)freq_fraction / (double)(1 << 28);
        printf("frequency = %.13f\n", frequency);
        int N1 = (buffer[0] & 0x1f) << 2 | buffer[1] >> 6;
        int HS_DIV_index = buffer[0] >> 5;
        printf("N1 field = %d\n", N1);
        printf("N1 value = %d\n", N1 + 1);
        printf("HS_DIV_index field = %d\n", HS_DIV_index);
        char HS_divisor[] = {4,5,6,7,-1,9,-1,11};
        printf("HS_DIV value = %d\n", HS_divisor[HS_DIV_index]);
        printf("f_out = %.13f\n", 114.285 * frequency / ((N1 + 1) * HS_divisor[HS_DIV_index]));
        
    } else {
        printf("No frequency or relay control\n");
    }

    // Generate array coordiates for the optimization and plotting routines
    get_array_coordinates();

    // initial mode
    array = 1;
    set_coefficients();

    // initial direction towards Europe
    //    bearing_angle = 45.0;
    bearing_angle = 67;
    relay_direction = 0;
    AdjustAngles();

    // initialize calibration and frequency
    ReadCalibrationFile(CAL_ANTENNA, band_center[band], avg_magnitude_ant, avg_mag_phase_ant);
    ReadCalibrationFile(CAL_RECEIVER, band_center[band], avg_magnitude_rec, avg_mag_phase_rec);
    SumCalibration();
    wave_length = FEET_PER_USEC / (float)band_center[band] * 1.0E6;
    SetFrequency((double)(band_center[band] * 8));


    int next_display_time = 0;
    int next_poll_time = SDL_GetTicks();
    for (;;) {
        while (next_poll_time > SDL_GetTicks()) {
            SDL_Delay(1);
        }
        next_poll_time += 10;
        while (SDL_PollEvent(&event)) {
        switch (event.type) {
        case SDL_USEREVENT:
            //                HandleUserEvents(&event);
            break;
            
        case SDL_KEYDOWN:
            // Handle any key presses here.  Remember this event.
            key = SDL_GetKeyName(event.key.keysym.sym);
            if (strcmp(key, "1") == 0) {
                if (shift_key) {
                    array_base_channel = 0;
                } else if (ctrl_key) {
                    reference_channel = 0;
                } else if (alt_key) {
                    elements = 1;  // 1 element
                    set_coefficients();
                } else
                    channel = 0;
            } else if (strcmp(key, "2") == 0) {
                if (shift_key) {
                    array_base_channel = 1;
                } else if (ctrl_key) {
                    reference_channel = 1;
                } else if (alt_key) {
                    elements = 2;  // 2 elements
                    set_coefficients();
                } else
                    channel = 1;
            } else if (strcmp(key, "3") == 0) {
                if (shift_key) {
                    array_base_channel = 2;
                } else if (ctrl_key) {
                    reference_channel = 2;
                } else if (alt_key) {
                    elements = 3;  // 3 elements
                    set_coefficients();
                } else
                    channel = 2;
            } else if (strcmp(key, "4") == 0) {
                if (shift_key) {
                    array_base_channel = 3;
                } else if (ctrl_key) {
                    reference_channel = 3;
                } else if (alt_key) {
                    elements = 4;  // 4 elements
                    set_coefficients();
                } else
                    channel = 3;
            } else if (strcmp(key, "5") == 0) {
                if (shift_key) {
                    array_base_channel = 4;
                } else if (ctrl_key) {
                    reference_channel = 4;
                } else if (alt_key) {
                    elements = 5;  // 5 elements
                    set_coefficients();
                } else
                    channel = 4;
            } else if (strcmp(key, "6") == 0) {
                if (shift_key) {
                    array_base_channel = 5;
                } else if (ctrl_key) {
                    reference_channel = 5;
                } else if (alt_key) {
                    elements = 6;  // 6 elements
                    set_coefficients();
                } else
                    channel = 5;
            } else if (strcmp(key, "7") == 0) {
                if (shift_key) {
                    array_base_channel = 6;
                } else if (ctrl_key) {
                    reference_channel = 6;
                } else if (alt_key) {
                    elements = 7;  // 7 elements
                    set_coefficients();
                } else
                    channel = 6;
            } else if (strcmp(key, "8") == 0) {
                if (shift_key) {
                    array_base_channel = 7;
                } else if (ctrl_key) {
                    reference_channel = 7;
                } else if (alt_key) {
                    elements = 8;  // 8 elements
                    set_coefficients();
                } else
                    channel = 7;
            } else if (shift_key && strcmp(key, "q") == 0) {
                bearing_angle += 90.0;
                AdjustAngles();
            } else if (strcmp(key, "j") == 0) {
                int local_coeff_scheme_index = coeff_scheme_index;
                int increment = 1;
                if (shift_key) {
                    increment = -1;
                }
                do {
                    local_coeff_scheme_index += increment;
                    if (local_coeff_scheme_index < 0) {
                        local_coeff_scheme_index = NUM_COEFF_SCHEMES - 1;
                    } else if (local_coeff_scheme_index == NUM_COEFF_SCHEMES) {
                        local_coeff_scheme_index = 0;
                    }
                    if (coeffs[local_coeff_scheme_index].elements == 0 ||
                        coeffs[local_coeff_scheme_index].elements == elements) {
                        break;
                    }
                } while (local_coeff_scheme_index != coeff_scheme_index);
                coeff_scheme_index = local_coeff_scheme_index;
                set_coefficients();
            } else if (strcmp(key, "a") == 0) {
                array = 1;  // array mode on
                set_coefficients();
            } else if (strcmp(key, "s") == 0) {
                array = 0;  // array mode off
                set_coefficients();
            } else if (strcmp(key, "[") == 0) {
                threshold *= 1.1;
            } else if (strcmp(key, "]") == 0) {
                threshold /= 1.1;
            } else if (alt_key && strcmp(key, "t") == 0) {
                tuning_mode = tuning_mode ? 0 : 1;
                if (FFT_N == 256)
                    tuning = 66;
                else if (FFT_N == 512)
                    tuning = 312;
                else if (FFT_N == 1024)
                    tuning = 1249;
            } else if (shift_key && strcmp(key, ".") == 0) {
                if (tuning < (FFT_N - int_scale))
                    tuning += int_scale;
            } else if (shift_key && strcmp(key, ",") == 0) {
                if (tuning >= int_scale)
                    tuning -= int_scale;
            } else if (strcmp(key, "left") == 0) {
                bearing_angle -= 0.1 * scale;
                AdjustAngles();
            } else if (strcmp(key, "right") == 0) {
                bearing_angle += 0.1 * scale;
                AdjustAngles();
            } else if (strcmp(key, "b") == 0) {
                band += 1;
                if (band == BAND_MAX)
                    band = 0;
                ReadCalibrationFile(CAL_ANTENNA, band_center[band], avg_magnitude_ant, avg_mag_phase_ant);
                ReadCalibrationFile(CAL_RECEIVER, band_center[band], avg_magnitude_rec, avg_mag_phase_rec);
                SumCalibration();
                wave_length = FEET_PER_USEC / (float)band_center[band] * 1.0E6;
                SetFrequency((double)(band_center[band] * 8));
                last_bearing = -1;  // force pattern redraw
            } else if (alt_key && strcmp(key, "p") == 0) {
                dump = elements;
            } else if (alt_key && strcmp(key, "k") == 0) {
                if (shift_key) {
                    if (sample_offset > -MAX_SYNC_SAMPLES)
                        sample_offset -= 1;
                } else {
                    if (sample_offset < MAX_SYNC_SAMPLES)
                        sample_offset += 1;
                }
            } else if (alt_key && strcmp(key, "c") == 0) {
                calibration_mode = CAL_ANTENNA;
                if (shift_key == 1)
                    calibration_mode = CAL_RECEIVER;
                calibration = calibration ? 0 : 1;
                if (calibration) {
                    int channel_index, sample_index;
                    for (channel_index = 0; channel_index < MAX_CHANNELS; channel_index++) {
#if 0
                        if (calibration_mode == CAL_ANTENNA) {
                            avg_magnitude_ant[channel_index] = 0;
                            avg_mag_phase_ant[channel_index] = 0;
                        } else {
                            avg_magnitude_rec[channel_index] = 0;
                            avg_mag_phase_rec[channel_index] = 0;
                        }
#endif
                        for (sample_index = 0; sample_index < MAX_SAMPLES; sample_index++) {
                            raw_magnitude[channel_index][sample_index] = 0;
                            raw_mag_phase[channel_index][sample_index] = 0;
                        }
                    }
                    sample_idx = 0;
                }
            } else if (strcmp(key, "l") == 0) {
                for (i = 0; i < elements; i++) {
                    printf("%d %9.3f %9.3f\n", i, multiplier[i].array_coeff, multiplier[i].array_phase);
                }
            } else if (strcmp(key, "r") == 0) {
                if (shift_key == 0) {
                    rotation = rotation ? 0 : 1;
                } else {
                    bearing_angle += 180.0;
                    AdjustAngles();
                }
            } else if (alt_key && strcmp(key, "e") == 0) {
                equalization = equalization ? 0 : 1;
            } else if (alt_key && strcmp(key, "u") == 0) {
                uni_directional = uni_directional ? 0 : 1;
                last_bearing = NAN;
            } else if (!shift_key && strcmp(key, ",") == 0) {
                correction = 1;
            } else if (!shift_key && strcmp(key, ".") == 0) {
                correction = 0;
            } else if (alt_key && strcmp(key, "x") == 0) {
                cleanup();
            } else if (!alt_key && strcmp(key, "x") == 0) {
                debug_flag = debug_flag ? 0 : 1;
                printf("debug flag %s\n", debug_flag ? "on" : "off");
                snapshot_count = 0;
            } else if (ctrl_key && strcmp(key, "z") == 0) {
                debug_flag3 = debug_flag3 ? 0 : 1;
            } else if (!alt_key && strcmp(key, "z") == 0) {
                debug_flag2 = debug_flag2 ? 0 : 1;
            } else if (alt_key && strcmp(key, "y") == 0) {
                bypass = bypass ? 0 : 1;
            } else if (alt_key && strcmp(key, "z") == 0) {
                UpdateCalibrationFile(CAL_RECEIVER, band_center[band], avg_magnitude_rec, avg_mag_phase_rec);
                UpdateCalibrationFile(CAL_ANTENNA, band_center[band], avg_magnitude_ant, avg_mag_phase_ant);
            } else if (alt_key && strcmp(key, "w") == 0) {
                char *name = "/home/vkean/calibrate.bin";
                cal_file = fopen(name, "w");
                for (i = 0; i < MAX_CHANNELS; i++) {
                    int records = fwrite(gain, sizeof(float), FFT_N, cal_file);
                    if (records != FFT_N) {
                        fprintf(stderr, "couldn't write \n");
                        break;
                    }
                    records = fwrite(phase, sizeof(float), FFT_N, cal_file);
                    if (records != FFT_N) {
                        fprintf(stderr, "couldn't write \n");
                        break;
                    }
                }
                fclose (cal_file);
                int uid = getpwnam("vkean")->pw_uid;
                int gid = getpwnam("vkean")->pw_gid;
                chown(name, uid, gid);
                name = "/home/vkean/calibrate.txt";
                cal_file = fopen(name, "w");
                fprintf(cal_file, "bin\tgain 1\tphase 1\tgain 2\tphase 2\n");
                for (i = 0; i < FFT_N; i++) {
                    if (gain[0][i] && gain[1][i]) {
                        fprintf(cal_file, "%4d\t", i > FFT_N / 2 ? i - FFT_N / 2 : i + FFT_N / 2);
                        for (j = 0; j < MAX_CHANNELS; j++) {
                            if ((fabs(gain[j][i] - 1.0) < 0.5) && (fabs(phase[j][i] * 180.0 / PI) < 5.0)) {
                                fprintf(cal_file, "%5.4f\t%5.4f\t", gain[j][i], phase[j][i] * 180.0 / PI);
                            } else {
                                fprintf(cal_file, "\t\t");
                            }
                        }
                        fprintf(cal_file, "\n");
                    }
                }
                fclose (cal_file);
            } else if (strcmp(key, "numlock") == 0) {
                bearing_angle = 0.0;
                AdjustAngles();
            } else if (strcmp(key, "[/]") == 0) {
                bearing_angle = 35.0;
                AdjustAngles();
            } else if (strcmp(key, "[*]") == 0) {
                bearing_angle = 45.0;
                AdjustAngles();
            } else if (strcmp(key, "[-]") == 0) {
                bearing_angle = 75.0;
                AdjustAngles();
            } else if (strcmp(key, "[+]") == 0) {
                bearing_angle = 90.0;
                AdjustAngles();
            } else if (strcmp(key, "enter") == 0) {
                bearing_angle = 150.0;
                AdjustAngles();
            } else if (strcmp(key, "[.]") == 0) {
                bearing_angle = 175.0;
                AdjustAngles();
            } else if (strcmp(key, "[0]") == 0) {
                bearing_angle = 240.0;
                AdjustAngles();
            } else if (strcmp(key, "[1]") == 0) {
                bearing_angle = 270.0;
                AdjustAngles();
            } else if (strcmp(key, "[4]") == 0) {
                bearing_angle = 295.0;
                AdjustAngles();
            } else if (strcmp(key, "[7]") == 0) {
                bearing_angle = 310.0;
                AdjustAngles();
            } else if (strcmp(key, "left ctrl") == 0) {
                ctrl_key = 1;
                left_ctrl = 1;
                scale = 10.0;
                int_scale = 10;
            } else if (strcmp(key, "left alt") == 0) {
                alt_key = 1;
                left_alt = 1;
                scale = 100.0;
                int_scale = 100;
            } else if (strcmp(key, "left shift") == 0) {
                shift_key = 1;
                left_shift = 1;
                scale = 0.1;
                int_scale = 1;
            } else if (strcmp(key, "right ctrl") == 0) {
                ctrl_key = 1;
                right_ctrl = 1;
                scale = 10.0;
                int_scale = 10;
            } else if (strcmp(key, "right alt") == 0) {
                alt_key = 1;
                right_alt = 1;
                scale = 100.0;
                int_scale = 100;
            } else if (strcmp(key, "right shift") == 0) {
                shift_key = 1;
                right_shift = 1;
                scale = 0.1;
                int_scale = 1;
            } else {
                printf("The %s key was pressed!\n", key);
            }
            break;
            
        case SDL_KEYUP:
            // Handle any key releases here.
            key = SDL_GetKeyName(event.key.keysym.sym);
            if (strcmp(key, "left ctrl") == 0) {
                left_ctrl = 0;
                scale = 1.0;
                int_scale = 1;
            } else if (strcmp(key, "left alt") == 0) {
                left_alt = 0;
                scale = 1.0;
                int_scale = 1;
            } else if (strcmp(key, "left shift") == 0) {
                left_shift = 0;
                scale = 1.0;
                int_scale = 1;
            } else if (strcmp(key, "right ctrl") == 0) {
                right_ctrl = 0;
                scale = 1.0;
                int_scale = 1;
            } else if (strcmp(key, "right alt") == 0) {
                right_alt = 0;
                scale = 1.0;
                int_scale = 1;
            } else if (strcmp(key, "right shift") == 0) {
                right_shift = 0;
                scale = 1.0;
                int_scale = 1;
            }
            if (left_shift == 0 && right_shift == 0) {
                shift_key = 0;
            }
            if (left_ctrl == 0 && right_ctrl == 0) {
                ctrl_key = 0;
            }
            if (left_alt == 0 && right_alt == 0) {
                alt_key = 0;
            }
            break;
            
        case SDL_MOUSEBUTTONDOWN:
            // Handle mouse clicks here.
            {
                int x_pos = event.motion.x, y_pos = event.motion.y;
                // offset the mouse position by the position of the compass box
                x_pos -= compass_position_x;
                y_pos -= compass_position_y;
                // offset the mouse position by the center of the remaining box
                x_pos -= center_x;
                y_pos -= center_y;
                // convert the position to polar coordinates (just to get the angle)
                bearing_angle = atan2(y_pos, x_pos) * DEGREES_PER_RADIAN;
                bearing_angle += 90.0;
                AdjustAngles();
            }
            break;

        case SDL_QUIT:
            cleanup();
            break;
            
        default:
            //printf("%d\n", event.type);
            break;
        }
        // update relay state
        if (serial_desc) {
            SetRTS(serial_desc, relay_direction);
        } else if (do_usb_control) {
        }
        }  // end while(SDL_PollEvent())
        if ((next_display_time < SDL_GetTicks())) {
            next_display_time += 100;
            gfx_thread(0);
        }
    }
    
    cleanup();

    /* We can't ever get here, but it keeps gcc quiet */
    return 0;
}

/* make_channel: Do all the jack hackery needed to wire up a single channel.  A bunch of input
 * ports for the individual receivers and a pair of output ports for the SDR */

void make_channel(jack_client_t *client, int i, char *port_name)
{
    char in_name[256], out_name[256];
    jack_port_t *port;
    int flags;

    snprintf(in_name, 255, "phasor_in_%d", i+1);
    fprintf(stderr, "registering '%s'\n", in_name);
    if (!(input_ports[i] = jack_port_register(client, in_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0))) {
        fprintf(stderr, "Cannot register '%s'\n", in_name);
        cleanup();
    }
    snprintf(in_name, 255, "%s:phasor_in_%d", client_name, i+1);

    snprintf(out_name, 255, "phasor_out_%d", i+1);
    fprintf(stderr, "registering '%s'\n", out_name);
    if (!(output_ports[i] = jack_port_register(client, out_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0))) {
        fprintf(stderr, "Cannot register '%s'\n", out_name);
        cleanup();
    }
    snprintf(out_name, 255, "%s:phasor_out_%d", client_name, i+1);
    
    if (do_jack_connect) {
        fprintf(stderr, "connecting '%s' to '%s'...\n", in_name, port_name);
        port = jack_port_by_name(client, port_name);
        if (port == NULL) {
            fprintf(stderr, "Can't find port '%s'\n", port_name);
            return;
        }
        flags = jack_port_flags(port);

        if (flags & JackPortIsInput) {
            const char **connections;
            unsigned int j;
            
            connections = jack_port_get_all_connections(client, port);
            for (j=0; connections && connections[j]; j++) {
                if (jack_disconnect(client, connections[j], port_name) == 0) {
                    //fprintf(stderr, "Broke '%s' to '%s'\n", possible_ports[j], port_name);
                    if (jack_connect(client, connections[j], in_name)) {
                        fprintf(stderr, "Could not connect '%s' to '%s'\n", connections[j], in_name);
                        cleanup();
                    }
                    //fprintf(stderr, "Connected '%s' to '%s'\n", possible_ports[j], in_name);
                } else {
                    fprintf(stderr, "Could not disconnect %s\n", connections[j]);
                }
            }
            free(connections);
            jack_connect(client, out_name, port_name);
            fprintf(stderr, "Connected '%s' to '%s'\n", out_name, port_name);
        } else if (flags & JackPortIsOutput && jack_connect(client, port_name, jack_port_name(input_ports[i]))) {
            fprintf(stderr, "Cannot connect port '%s' to '%s'\n", port_name, in_name);
            cleanup();
        }
    }
}

void cleanup()
{
    unsigned int i, j, l;
    const char **all_iports, **all_oports;

    if (serial_desc) {
        SetRTS(serial_desc, 1);
        ReleaseSerialPort(serial_desc);
    }

    if (usb_handle) {
        usb_close(usb_handle);
    }

    for (i=0; i<num_inputs; i++) {
        if (input_ports[i] == NULL || output_ports[i] == NULL) {
            continue;
        }

        all_iports = jack_port_get_all_connections(client, output_ports[i]);
        all_oports = jack_port_get_all_connections(client, input_ports[i]);

        for (j=0; all_oports && all_oports[j]; j++) {
            jack_disconnect(client, all_oports[j], jack_port_name(input_ports[i]));
        }

        for (j=0; all_iports && all_iports[j]; j++) {
            jack_disconnect(client, jack_port_name(output_ports[i]), all_iports[j]);
            for (l=0; all_oports && all_oports[l]; l++) {
                jack_connect(client, all_oports[l], all_iports[j]);
            }
        }
    }

    /* Leave the jack graph */
    jack_client_close(client);

    fftw_destroy_plan(fft_plan_inv);
    fftw_destroy_plan(fft_plan);
    fftw_free(fft_in_inv);
    fftw_free(fft_out_inv);
    fftw_free(fft_in);
    fftw_free(fft_out);

    /* And were done */
    exit(0);
}

int store(char *path, void *data)
{
    char cmdpath[256];
    int i;
    FILE *cmd;

    printf("Store request received: %s\n", path);
    snprintf(cmdpath, 255, "%s/start", path);
    if (!(cmd = fopen(cmdpath, "w"))) {
        printf("Cannot store state in '%s'\n", cmdpath);
        return 0;
    }
    fprintf(cmd, "phasor -n '%s' ", client_name);
    for (i=0; i<num_inputs; i++) {
        fprintf(cmd, " -");
    }
    fprintf(cmd, "\n");
    fclose(cmd);

    return 0;
}

char *mode_string[3] = {"Single", "Array", "End Fire"};
char *relay_string[2] = {"Off", "On"};
char status_string[1024];
char index_string[10];

int FormatString(char *buffer[], int *room, char *format, ...) {
    va_list arg;
    int bytes;

    va_start(arg, format);
    bytes = vsnprintf(*buffer, *room, format, arg);
    *buffer += bytes;
    *room -= bytes;
    va_end(arg);
    return bytes;
}

#define FREQ(bin) (((bin) > FFT_N / 2 ? (bin) - FFT_N / 2 : (bin) + FFT_N / 2) * 96000 / FFT_N + band_center[band] - 48000)

int gfx_thread(void *foo)
{
    SDL_Rect buf_rect;
    SDL_Color foreground = {255, 255, 255, 0};
    SDL_Color background = {0, 0, 0, 0};
    int c, height, width;
    int buffer_space_left;
    char *string_pointer;
    char substring_buffer1[MAX_CHANNELS * 10];
    char substring_buffer2[MAX_CHANNELS * 10];
    char substring_buffer3[MAX_CHANNELS * 10];
    char substring_buffer4[MAX_CHANNELS * 10];
    char substring_buffer5[MAX_CHANNELS * 10];
    char substring_buffer6[MAX_CHANNELS * 10];
    char substring_buffer7[MAX_CHANNELS * 10];
    char substring_buffer8[MAX_CHANNELS * 10];
    char *substring_pointer1, *substring_pointer2, *substring_pointer3, *substring_pointer4;
    char *substring_pointer5, *substring_pointer6, *substring_pointer7, *substring_pointer8;
    int buffer1_space_left, buffer2_space_left, buffer3_space_left, buffer4_space_left;
    int buffer5_space_left, buffer6_space_left, buffer7_space_left, buffer8_space_left;

    TTF_SizeText(font, "Test", &width, &height);

    if (number_receivers > 4) {
        buf_rect.w = 960;
        buf_rect.h = 320;
    } else {
        buf_rect.w = 640;
        buf_rect.h = 320;
    }
    buf_rect.x = compass_position_x;
    buf_rect.y = compass_position_y;
    if (bearing_angle != last_bearing) {
        DrawCompass(screen, &dest);
        last_bearing = bearing_angle;
    }

    if (number_receivers > 4) {
        buf_rect.w = 960;
    } else {
        buf_rect.w = 640;
    }

    int x = max_bin > FFT_N / 2 ? max_bin - FFT_N / 2 : max_bin + FFT_N / 2;
    int Frequency = x * 96000 / FFT_N + band_center[band] - 48000;
    snprintf(status_string, sizeof(status_string),
             "Phasor " VERSION "    "
             "Mode: %s\n"
             "Elements: %d, Spacing: %4.1f, Wavelength: %4.1f \n ",
             bypass ? "Bypass" : calibration ? "Calibrate" : mode_string[array],
             elements, element_spacing, wave_length);
    buf_rect.x = 0; buf_rect.y = 0; buf_rect.h = 100;
    PrintStrings(screen, font, status_string, &buf_rect, foreground, background);
    string_pointer = status_string;
    buffer_space_left = sizeof(status_string);
    FormatString(&string_pointer, &buffer_space_left,
             "Bearing: %5.1f, Steering: %6.1f Relay: %s\n"
             "Channel: %d, Array Base: %d, Ref Rec: %d, Sync: %d  \n ",
             bearing_angle, steering_angle * DEGREES_PER_RADIAN, relay_string[relay_direction],
             channel + 1, array_base_channel + 1, reference_channel + 1, sample_offset);
    buf_rect.x = 0; buf_rect.y = height * 3; buf_rect.h = 100;
    PrintStrings(screen, font, status_string, &buf_rect, foreground, background);

    index_string[0] = 0;
    if (calibration) {
        snprintf(index_string, sizeof(index_string), "[%2d]", sample_idx);
    }
    substring_pointer1 = substring_buffer1;
    buffer1_space_left = sizeof(substring_buffer1);
    substring_pointer2 = substring_buffer2;
    buffer2_space_left = sizeof(substring_buffer2);
    substring_pointer3 = substring_buffer3;
    buffer3_space_left = sizeof(substring_buffer3);
    substring_pointer4 = substring_buffer4;
    buffer4_space_left = sizeof(substring_buffer4);
    substring_pointer5 = substring_buffer5;
    buffer5_space_left = sizeof(substring_buffer5);
    substring_pointer6 = substring_buffer6;
    buffer6_space_left = sizeof(substring_buffer6);
    substring_pointer7 = substring_buffer7;
    buffer7_space_left = sizeof(substring_buffer7);
    substring_pointer8 = substring_buffer8;
    buffer8_space_left = sizeof(substring_buffer8);
    for (c = 0; c < elements; c++) {
        FormatString(&substring_pointer1, &buffer1_space_left, "%6.3f", avg_magnitude_ant[c]);
        FormatString(&substring_pointer2, &buffer2_space_left, "%6.1f", avg_mag_phase_ant[c] * DEGREES_PER_RADIAN);
        FormatString(&substring_pointer3, &buffer3_space_left, "%6.3f", avg_magnitude_rec[c]);
        FormatString(&substring_pointer4, &buffer4_space_left, "%6.1f", avg_mag_phase_rec[c] * DEGREES_PER_RADIAN);
        FormatString(&substring_pointer5, &buffer5_space_left, "%6.3f", magnitude[c]);
        FormatString(&substring_pointer6, &buffer6_space_left, "%6.1f", reference_channel == c ? 0.0 : mag_phase[c] * DEGREES_PER_RADIAN);
        FormatString(&substring_pointer7, &buffer7_space_left, "%6.3f", multiplier[c].array_coeff);
        FormatString(&substring_pointer8, &buffer8_space_left, "%6.1f", multiplier[c].array_phase);
    }
    string_pointer = status_string;
    buffer_space_left = sizeof(status_string);
    FormatString(&string_pointer, &buffer_space_left,
                 "  Ant Mag: %s %s\n", substring_buffer1, index_string);
    FormatString(&string_pointer, &buffer_space_left,
                 "Ant Phase: %s \n", substring_buffer2);
    FormatString(&string_pointer, &buffer_space_left,
                 "  Rec Mag: %s \n", substring_buffer3);
    FormatString(&string_pointer, &buffer_space_left,
                 "Rec Phase: %s \n", substring_buffer4);
    FormatString(&string_pointer, &buffer_space_left,
                 "  Res Mag: %s %s\n", substring_buffer5);
    FormatString(&string_pointer, &buffer_space_left,
                 "Res Phase: %s    \n", substring_buffer6, equalization && !calibration ? "(e)" : "   ");
    FormatString(&string_pointer, &buffer_space_left, "%s %s \n", debug_flag ? "x" : "", debug_flag3 ? "z" : "");
    FormatString(&string_pointer, &buffer_space_left, "%s \n", coeffs[coeff_scheme_index].name);
    FormatString(&string_pointer, &buffer_space_left,
                 "  Ele Mag: %s %s\n", substring_buffer7, index_string);
    FormatString(&string_pointer, &buffer_space_left,
                 "Ele Phase: %s \n", substring_buffer8);
    FormatString(&string_pointer, &buffer_space_left, " \n"
             "threshold: %6.3f\n"
             "Freq: %d%s, bin: %d, index: %d\n \n \n \n ",

             threshold,
             Frequency, tuning_mode ? " ^" : "", max_bin, x);
    buf_rect.x = 0; buf_rect.y = 100; buf_rect.w = 320; buf_rect.h = 220;
    PrintStrings(screen, font, status_string, &buf_rect, foreground, background);
    SDL_UpdateRects(screen, 1, &win);
    
    return 0;
}

jack_default_audio_sample_t sample_sync_x[2][MAX_CHANNELS][MAX_SYNC_SAMPLES];
jack_default_audio_sample_t sample_sync_y[2][MAX_CHANNELS][MAX_SYNC_SAMPLES];
int last_sync_index, sync_index;

int process_ring(jack_nframes_t nframes, void *arg)
{
    unsigned int c, i, in_port, out_port;
    float x, y;
    jack_default_audio_sample_t *in_x, *in_y, *out_x, *out_y;
    
    /* just in case the ports aren't registered yet */
    for (i = 0; i < num_inputs; i++) {
        if (input_ports[i] == 0) {
            return 0;
        }
    }

    // if necessary for debugging, copy the input from channel 0 to the output and bail
    if (bypass) {
        in_x = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[0], nframes);
        in_y = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[1], nframes);
        out_x = (jack_default_audio_sample_t *) jack_port_get_buffer(output_ports[0], nframes);
        out_y = (jack_default_audio_sample_t *) jack_port_get_buffer(output_ports[1], nframes);
        for (i = 0; i < nframes; i++) {
            out_x[i] = in_x[i];
            out_y[i] = in_y[i];
        }
        return 0;
    }

    if (invert_iq) {
        in_x = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[1], nframes);
        in_y = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[0], nframes);
    } else {
        in_x = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[0], nframes);
        in_y = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[1], nframes);
    }

    // negate port 0 and port 1 data to correct for extra inversion in Delta 1010LT.  note that
    // the buffer pointers are still set from above
    for (i = 0; i < nframes; i++) {
        in_x[i] = -in_x[i];
        in_y[i] = -in_y[i];
    }
    if (number_receivers > 4) {
    if (invert_iq) {
        in_x = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[9], nframes);
        in_y = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[8], nframes);
    } else {
        in_x = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[8], nframes);
        in_y = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[9], nframes);
    }
    // negate port 8 and port 9 data to correct for extra inversion in Delta 1010LT.
    for (i = 0; i < nframes; i++) {
        in_x[i] = -in_x[i];
        in_y[i] = -in_y[i];
    }
    }

    // all processing drives a single output channel (port pair)
    out_port = 0;
    out_x = (jack_default_audio_sample_t *) jack_port_get_buffer(output_ports[out_port], nframes);
    out_y = (jack_default_audio_sample_t *) jack_port_get_buffer(output_ports[out_port + 1], nframes);

    // begin main processing loop - iterate every intended receiver.  Start with the reference
    // receiver, because the others reference it (hence the name).
    for (c = 0, in_port = reference_channel * 2; c < elements; c++, in_port += 2) {

        if (in_port >= (reference_channel + elements) * 2) {
            in_port = 0;
        }
        int last_receiver = (c + 1) == elements;

        int iq_port = in_port / 2;

        if (invert_iq) {
            in_x = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[in_port + 1], nframes);
            in_y = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[in_port + 0], nframes);
        } else {
            in_x = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[in_port + 0], nframes);
            in_y = (jack_default_audio_sample_t *) jack_port_get_buffer(input_ports[in_port + 1], nframes);
        }
            
    // Most of the time, a buffer full of samples from a second sound card are delayed (or
    // leading) the first sound card by some number of samples.  Remove the excess samples from
    // the delayed buffer and save them to append to the buffer on the next go-round.
    //
    // Note that the sample_offset variable is negative if the first card lags the second card
    // and is positive if the first card leads the second card.  Zero means we don't have to do
    // anything here.
    //
    // Note that the sample_sync buffer is double buffered, and the index is either 0 or 1. 

    if ((sample_offset > 0) && (c >= (MAX_CHANNELS / 2))) {
        // move sample_offset samples to the next syncing buffer, move the rest "over" in
        // the input buffer, and move from the previous syncing buffer to the input buffer
        //
        // Note the weird loop controls because i is unsigned
        for (i = nframes - 1; i < nframes; i--) {
            if (i > nframes - sample_offset - 1) {
                sample_sync_x[sync_index][c][i - (nframes - sample_offset)] = in_x[i];                    
                sample_sync_y[sync_index][c][i - (nframes - sample_offset)] = in_y[i];                    
                in_x[i] = in_x[i - sample_offset];
                in_y[i] = in_y[i - sample_offset];
            } else if (i < sample_offset) {
                in_x[i] = sample_sync_x[last_sync_index][c][i];
                in_y[i] = sample_sync_y[last_sync_index][c][i];
            } else {
                in_x[i] = in_x[i - sample_offset];
                in_y[i] = in_y[i - sample_offset];
            }
        }
        if (last_receiver) {
            last_sync_index = sync_index;
            sync_index += 1;
            if (sync_index == 2)
                sync_index = 0;
        }
    } else if (sample_offset < 0 && c < MAX_CHANNELS / 2) {
        for (i = nframes - 1; i < nframes; i--) {
            if (i > nframes + sample_offset - 1) {
                sample_sync_x[sync_index][c][i - (nframes + sample_offset)] = in_x[i];                    
                sample_sync_y[sync_index][c][i - (nframes + sample_offset)] = in_y[i];                    
                in_x[i] = in_x[i + sample_offset];
                in_y[i] = in_y[i + sample_offset];
            } else if (i < -sample_offset) {
                in_x[i] = sample_sync_x[last_sync_index][c][i];
                in_y[i] = sample_sync_y[last_sync_index][c][i];
            } else {
                in_x[i] = in_x[i + sample_offset];
                in_y[i] = in_y[i + sample_offset];
            }
        }
        if (last_receiver) {
            last_sync_index = sync_index;
            sync_index += 1;
            if (sync_index == 2)
                sync_index = 0;
        }
    } else if (sample_offset < 0 && c < MAX_CHANNELS / 2) {
    }

        // start in the time domain
        frequency_domain = 0;

        // do channel equalization if desired - currently this is done based on a single
        // calibration frequency, so the correction is performed in the time domain.  We do
        // equalization before IQ balancing assuming that frequency independent scaling and
        // phase shifting does not alter the IQ balance.
        if (equalization) {
            double r, q;
            if (!calibration) {
                for (i = 0; i < nframes; i++) {
                    complex w = (in_x[i] + I * in_y[i]);
                    r = cabs(w);
                    if (avg_magnitude_sys[iq_port])
                        r /= avg_magnitude_sys[iq_port];
                    q = carg(w) - avg_mag_phase_sys[iq_port];
                    w = r * cexp(I * q);
                    in_x[i] = creal(w);
                    in_y[i] = cimag(w);
                }
            } else if (calibration_mode == CAL_ANTENNA) {
                for (i = 0; i < nframes; i++) {
                    complex w = (in_x[i] + I * in_y[i]);
                    r = cabs(w);
                    if (avg_magnitude_rec[iq_port])
                        r /= avg_magnitude_rec[iq_port];
                    q = carg(w) - avg_mag_phase_rec[iq_port];
                    w = r * cexp(I * q);
                    in_x[i] = creal(w);
                    in_y[i] = cimag(w);
                }
            }
        }

        // do IQ balancing if desired
        if (correction) {
            if (iq_count++ > (nframes_max / FFT_N)) {
                //ComputeIQBalance(iq_port, in_x, in_y);
                iq_count = 0;
            }
            //BalanceIQ(in_x, in_y);
        }

        if (calibration) {
            // move to the frequency domain if not there already
            GoFrequencyDomain(in_x, in_y);

            max_bin_valid = 0;
            if (tuning_mode == 0) {
                // scan for bin with highest magnitude above some threshold and avoid the edges
                // and the middle of the passband
                complex max_so_far = 0;
                for (i = 0; i < FFT_N; i++) {
                    // avoid the center
                    if (avoid_center && abs(i - FFT_N/2) < 3)
                        continue;
                    // disregard signals below the threshold
                    if (cabs(fft_out[i]) < threshold)
                        continue;
                    // remember strongest bin
                    if (cabs(fft_out[i]) > cabs(max_so_far)) {
                        max_so_far = fft_out[i];
                        max_bin = i;
                        max_bin_valid = 1;
                    }
                }
            } else {
                // scan for bin selected by tuning for highest magnitude
                complex max_so_far = 0;
                // select bin by tuning
                max_bin = tuning;
                for (i = tuning < 15 ? 0 : tuning - 15; i < (tuning >= FFT_N - 15 ? FFT_N : tuning + 15); i++) {
                    // disregard signals below the threshold
                    if (cabs(fft_out[i]) < threshold)
                        continue;
                    if (cabs(fft_out[i]) > cabs(max_so_far)) {
                        max_so_far = fft_out[i];
                        max_bin = i;
                        max_bin_valid = 1;
                    }
                }
            }

            if (max_bin_valid) {
                int sample_index = sample_idx;
                if (iq_port == reference_channel) {
                    ref_magnitude[sample_index] = cabs(fft_out[max_bin]);
                    ref_mag_phase[sample_index] = carg(fft_out[max_bin]);
                    raw_magnitude[iq_port][sample_index] = cabs(fft_out[max_bin]);
                    raw_mag_phase[iq_port][sample_index] = carg(fft_out[max_bin]);
                    // pre-scale input to average computation
                    raw_magnitude[iq_port][sample_index] /= MAX_SAMPLES;
                    raw_mag_phase[iq_port][sample_index] /= MAX_SAMPLES;
                    // reference channel has fixed gain and phase
                    if (calibration_mode == CAL_ANTENNA) {
                        avg_magnitude_ant[iq_port] = 1.0;
                        avg_mag_phase_ant[iq_port] = 0.0;
                    } else {
                        avg_magnitude_rec[iq_port] = 1.0;
                        avg_mag_phase_rec[iq_port] = 0.0;
                    }
                } else {
                    if (ref_magnitude[sample_index])
                        raw_magnitude[iq_port][sample_index] =
                            cabs(fft_out[max_bin]) / ref_magnitude[sample_index];
                    raw_mag_phase[iq_port][sample_index] = 
                        carg(fft_out[max_bin]) - ref_mag_phase[sample_index];
                    // normalize to 0 to 2 * PI
                    if (raw_mag_phase[iq_port][sample_index] >= 2.0 * PI) {
                        raw_mag_phase[iq_port][sample_index] -= 2.0 * PI;
                    } else if (raw_mag_phase[iq_port][sample_index] < 0) {
                        raw_mag_phase[iq_port][sample_index] += 2.0 * PI;
                    }
                    // pre-scale input to average computation
                    raw_magnitude[iq_port][sample_index] /= MAX_SAMPLES;
                    raw_mag_phase[iq_port][sample_index] /= MAX_SAMPLES;
                    // compute average magnitude and phase
                    if (calibration_mode == CAL_ANTENNA) {
                        avg_magnitude_ant[iq_port] = 0.0;
                        avg_mag_phase_ant[iq_port] = 0.0;
                        for (i = 0; i < MAX_SAMPLES; i++) {
                            avg_magnitude_ant[iq_port] += raw_magnitude[iq_port][i];
                            avg_mag_phase_ant[iq_port] += raw_mag_phase[iq_port][i];
                        }
                        // normalize to -PI to PI by subtracting 2 * PI
                        if (avg_mag_phase_ant[iq_port] > PI) {
                            avg_mag_phase_ant[iq_port] -= 2.0 * PI;
                        }
                    } else {
                        avg_magnitude_rec[iq_port] = 0.0;
                        avg_mag_phase_rec[iq_port] = 0.0;
                        for (i = 0; i < MAX_SAMPLES; i++) {
                            avg_magnitude_rec[iq_port] += raw_magnitude[iq_port][i];
                            avg_mag_phase_rec[iq_port] += raw_mag_phase[iq_port][i];
                        }
                        // normalize to -PI to PI by subtracting 2 * PI
                        if (avg_mag_phase_rec[iq_port] > PI) {
                            avg_mag_phase_rec[iq_port] -= 2.0 * PI;
                        }
                    }
                    // update combined calibration data
                    SumCalibration();
                }
            }  // end of if (max_bin_valid)

            complex signal = fft_out[max_bin];
            complex image = fft_out[FFT_N - max_bin];
            complex Z = signal * image;

            double power = creal(signal) * creal(signal) +
                cimag(signal) * cimag(signal) +
                creal(image) * creal(image) +
                cimag(image) * cimag(image);

            complex Zprime = creal(Z) / power + I * cimag(Z) / power;
            
            double Temp = sqrt(1.0 - 4.0 * creal(Zprime) * creal(Zprime));
            double Phase = asin(2 * cimag(Zprime) / Temp);
            double Gain = Temp / (1.0 - 2.0 * creal(Zprime));

            gain[iq_port][max_bin] *= count[iq_port][max_bin];
            gain[iq_port][max_bin] += Gain;
            gain[iq_port][max_bin] /= count[iq_port][max_bin] + 1;
            phase[iq_port][max_bin] *= count[iq_port][max_bin];
            phase[iq_port][max_bin] += Phase;
            phase[iq_port][max_bin] /= count[iq_port][max_bin] + 1;
            count[iq_port][max_bin] += 1;

            if (last_receiver) {
                // bump and wrap sample buffer index
                sample_idx += 1;
                if (sample_idx == MAX_SAMPLES) {
                    sample_idx = 0;
                }
            }
        }  // end of if (calibration)

        magnitude[iq_port] = 0;
        
        // move to the frequency domain if not there already
        GoFrequencyDomain(in_x, in_y);

        max_bin_valid = 0;
        if (tuning_mode == 0) {
            // scan for bin with highest magnitude
            complex max_so_far = 0;
            for (i = 0; i < FFT_N; i++) {
                // avoid the center
                if (avoid_center && (abs(i - FFT_N/2) < 3))
                    continue;
                // disregard signals below the threshold
                if (cabs(fft_out[i]) < threshold) 
                    continue;
                    // update max bin
                if (cabs(fft_out[i]) > cabs(max_so_far)) {
                    max_so_far = fft_out[i];
                    max_bin = i;
                    max_bin_valid = 1;
                }
            }
        } else {
            // scan near bin selected by tuning for highest magnitude
            complex max_so_far = 0;
            // select bin by tuning
            max_bin = tuning;
            for (i = tuning < 15 ? 0 : tuning - 15; i < (tuning >= FFT_N - 15 ? FFT_N : tuning + 15); i++) {
                    // stay inside the array
                if ((i < 0) || (i >= FFT_N))
                    continue;
                // disregard signals below an arbitrary, empirical threshold
                if (cabs(fft_out[i]) < threshold) 
                    continue;
                if (cabs(fft_out[i]) > cabs(max_so_far)) {
                    max_so_far = fft_out[i];
                    max_bin = i;
                    max_bin_valid = 1;
                }
            }
        }
        
        if (max_bin_valid) {
            magnitude[iq_port] = cabs(fft_out[max_bin]);
            if (iq_port == reference_channel) {
                mag_phase[iq_port] = carg(fft_out[max_bin]);
            } else {
                mag_phase[iq_port] = carg(fft_out[max_bin]) - mag_phase[reference_channel];
            }
            // normalize to -PI to PI
            if (mag_phase[iq_port] > PI) {
                mag_phase[iq_port] -= 2.0 * PI;
            } else if (mag_phase[iq_port] < -PI) {
                mag_phase[iq_port] += 2.0 * PI;
            }
        }

        if (array == 0) {
            // copy this channel to the output if appropriate
            if (iq_port == channel) {
                for (i = 0; i < nframes; i++) {
                    out_x[i] = in_x[i];
                    out_y[i] = in_y[i];
                }
            }
        } else if (array == 1) {
            double k = 2.0 * PI * element_spacing / wave_length;
            if (channel >= array_base_channel) {
                complex af = 0, iq = 0;
                for (i = 0; i < nframes; i++) {
                    x = in_x[i];
                    y = in_y[i];
                    // Apply beam forming algorithm
                    double angle, exp;
                    if (array_type == TYPE_LINEAR) {
                        if (uni_directional) {
                            angle = (steering_angle > PI / 2.0) || (steering_angle < -PI / 2.0) ? PI - steering_angle : steering_angle;
                        } else {
                            angle = steering_angle;
                        }
                        if (angle > PI) {
                            angle -= 2.0 * PI;
                        }
                        if (coeff_scheme_index == 0) {
                            exp = -multiplier[iq_port].array_phase * RADIANS_PER_DEGREE;
                        } else {
                            exp = iq_port * k * sin(angle);
                            multiplier[iq_port].array_phase = iq_port * k * sin(angle) * DEGREES_PER_RADIAN;
                        }
                        af = multiplier[iq_port].array_coeff / gain_compensation * cexp(I * exp);
                        iq = x + I * y;
                    } else if (array_type == TYPE_CIRCLE) {
                        exp = multiplier[iq_port].array_phase * RADIANS_PER_DEGREE;
                        af = multiplier[iq_port].array_coeff / gain_compensation * cexp(I * exp);
                        iq = x + I * y;
                    }
#if 0
                    if (debug_flag && i == 0 && (SDL_GetTicks() % 1000) == 0) {
                        printf("%d: %f %f\n", c, cabs(af * iq), carg(af * iq) * DEGREES_PER_RADIAN);
                    }
#endif
                    if (debug_flag && i == 0) {
                        snapshot_af[iq_port] = af;
                        snapshot_iq[iq_port] = iq;
                        snapshot_count += 1;
                        if (snapshot_count == MAX_CHANNELS) {
                            int index;
                            for (index = 0; index < MAX_CHANNELS; index++) {
                                printf("%d: %f %f", index, cabs(snapshot_af[index]), carg(snapshot_af[index]) * DEGREES_PER_RADIAN);
                                printf("  %f %f\n", cabs(snapshot_iq[index]), carg(snapshot_iq[index]) * DEGREES_PER_RADIAN);
                            }
                            debug_flag = 0;
                        }
                    }
                    if (in_port == array_base_channel * 2) {
                        out_x[i] = creal(af * iq);
                        out_y[i] = cimag(af * iq);
                    } else {
                        out_x[i] += creal(af * iq);
                        out_y[i] += cimag(af * iq);
                    }
                }
            }
        }  // if (array == 1)

        if (dump) {
            char name[80];
            strcpy(name, "/home/vkean/dump?.bin");
            char *p = strchr(name, '?');
            *p = (iq_port) + '0';
            if (dump_file[iq_port] == 0) {
                fprintf(stdout, "Opening \"%s\"\n", name);
                dump_file[iq_port] = fopen(name, "w");
                dump_count[iq_port] = 0;
                dump_bytes[iq_port] = 0;
            }
            if (dump_file[iq_port] == 0) {
                dump = 0;
                dump_count[iq_port] = 0;
                fprintf(stderr, "Can't open \"%s\" for writing\n", name);
                goto dump_done;
            }
            for (i = 0; i < nframes; i++) {
                dump_bytes[iq_port] += fwrite((void *)&in_x[i], sizeof(float), 1, dump_file[iq_port]);
                dump_bytes[iq_port] += fwrite((void *)&in_y[i], sizeof(float), 1, dump_file[iq_port]);
            }
            dump_count[iq_port] += nframes;
            if (dump_count[iq_port] > 96000) {
                fclose(dump_file[iq_port]);
                fprintf(stdout, "Wrote %d bytes on \"%s\"\n", dump_bytes[iq_port], name);
                dump_count[iq_port] = 0;
                if (dump > 0)
                    dump -= 1;
            }
#if 0
            int sample_index;
            strcpy(name, "/home/vkean/dump?.txt");
            char *p = strchr(name, '?');
            *p = (iq_port) + '0';
            if (dump_file[iq_port] == 0) {
                fprintf(stdout, "Opening \"%s\"\n", name);
                dump_file[iq_port] = fopen(name, "w");
                dump_count[iq_port] = 0;
                dump_bytes[iq_port] = 0;
            }
            if (dump_file[iq_port] == 0) {
                dump = 0;
                dump_count[iq_port] = 0;
                fprintf(stderr, "Can't open \"%s\" for writing\n", name);
                break;
            }
            for (i = 0; i < MAX_SAMPLES; i++) {
                dump_bytes[iq_port] += fprintf(dump_file[iq_port], "%f %f\n", raw_magnitude[iq_port][i] * (float)MAX_SAMPLES, raw_mag_phase[iq_port][i] * (float)MAX_SAMPLES * DEGREES_PER_RADIAN);
            }
            avg_magnitude[iq_port] = 0.0;
            avg_mag_phase[iq_port] = 0.0;
            for (sample_index = 0; sample_index < MAX_SAMPLES; sample_index++) {
                avg_magnitude[iq_port] += raw_magnitude[iq_port][sample_index];
                avg_mag_phase[iq_port] += raw_mag_phase[iq_port][sample_index];
            }                    
            dump_bytes[iq_port] += fprintf(dump_file[iq_port], "\n%f %f\n", avg_magnitude[iq_port], avg_mag_phase[iq_port] * DEGREES_PER_RADIAN);
            // the reference port should always be constant
            if (in_port == 0) {
                avg_magnitude[iq_port] = 1.0;
                avg_mag_phase[iq_port] = 0.0;
            }
            fclose(dump_file[iq_port]);
            int uid = getpwnam("vkean")->pw_uid;
            int gid = getpwnam("vkean")->pw_gid;
            chown(name, uid, gid);
            fprintf(stdout, "Wrote %d bytes on \"%s\"\n", dump_bytes[iq_port], name);
            dump_count[iq_port] = 0;
#endif
#if 0
            strcpy(name, "/home/vkean/dump_iq?.txt");
            char *p = strchr(name, '?');
            *p = (iq_port) + '0';
            if (dump_file[iq_port] == 0) {
                fprintf(stdout, "Opening \"%s\"\n", name);
                dump_file[iq_port] = fopen(name, "w");
                dump_count[iq_port] = 0;
                dump_bytes[iq_port] = 0;
            }
            if (dump_file[iq_port] == 0) {
                dump = 0;
                dump_count[iq_port] = 0;
                fprintf(stderr, "Can't open \"%s\" for writing\n", name);
                break;
            }
            dump_bytes[iq_port] += DumpIQPoints(dump_file[iq_port], iq_port);
            fclose(dump_file[iq_port]);
            int uid = getpwnam("vkean")->pw_uid;
            int gid = getpwnam("vkean")->pw_gid;
            chown(name, uid, gid);
            fprintf(stdout, "Wrote %d bytes on \"%s\"\n", dump_bytes[iq_port], name);
            dump_count[iq_port] = 0;
#endif
            if (last_receiver)
                dump = 0;
        } // end of if (dump)
dump_done:
        ;
    } // end of receiver loop

    return 0;
}
    
